// Copyright (C) 2009 Jesse Jones
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using MmmDoc;
using Mono.Cecil;
using System;
using System.Collections.Generic;

namespace MmmDoc.Database
{
	internal sealed class MethodMember : Member
	{
		public MethodMember(Document doc, TypeDef type, MethodDefinition member) : base(type, member.ToString())
		{
			Key = DoGetMethodName(member);
			Access = (MemberAccess) (member.Attributes & MethodAttributes.MemberAccessMask);
			Modifiers = GetModifiers(member);
			Name = DoGetMethodName(member);
			
			ReturnType = TypeName.Create(doc, member.ReturnType.ReturnType);
			Arguments = new Arguments(doc, member);
			GenericArgs = GetGenericArgs(doc, member);
			
			var attrs = new List<string>();
			member.CustomAttributes.AddAttributes(attrs);
			foreach (SecurityDeclaration sec in member.SecurityDeclarations)
			{
				attrs.Add(sec.ToText(false));
			}
			if (member.ImplAttributes >= MethodImplAttributes.Unmanaged)
				attrs.Add(member.ImplAttributes.ToText());
			CustomAttributes = attrs.ToArray();
			
			MemberName = member.Name;
		}
		
		public TypeName ReturnType {get; private set;}
		
		public Arguments Arguments {get; private set;}
		
		/// <summary>Null if the type is not generic.</summary>
		/// <remarks>These may be or may not be instantiated.</remarks>
		public TypeName[] GenericArgs {get; private set;}
		
		internal override void Disambiguate()
		{
			var builder = new System.Text.StringBuilder();
			
			builder.Append(Name);
			
			builder.Append(Arguments.Opener);
			for (int i = 0; i < Arguments.Names.Length; ++i)
			{
				if (Arguments.Modifiers[i] != null)
					builder.Append(Arguments.Modifiers[i]);
				
				builder.Append(Arguments.Types[i].Name);
				builder.Append(" ");
				builder.Append(Arguments.Names[i]);
				
				if (i + 1 < Arguments.Names.Length)
					builder.Append(", ");
			}
			builder.Append(Arguments.Closer);
			
			Key = builder.ToString();
		}
		
		internal override SortKey GetSortKey()
		{
			return new SortKey{Name = Name, SubWeight = Arguments.Names.Length};
		}
		
		internal override string GetXmlName(TypeDef type)
		{
			var builder = new System.Text.StringBuilder();
			
			builder.Append("M:");
			
			builder.Append(type.GetXmlName());
			builder.Append('.');
			builder.Append(MemberName);
			
			if (GenericArgs != null && GenericArgs.Length > 0)
			{
				builder.Append("``");
				builder.Append(GenericArgs.Length.ToString());
			}
			
			if (Arguments.Types.Length > 0)
			{
				builder.Append('(');
				for (int i = 0; i < Arguments.Types.Length; ++i)
				{
					builder.Append(Arguments.Types[i].GetXmlName());
					
					if (Arguments.Modifiers[i].Contains("out "))
						builder.Append('@');
					
					if (i + 1 < Arguments.Types.Length)
						builder.Append(',');
				}
				builder.Append(')');
			}
			
			if (MemberName == "op_Explicit" || MemberName == "op_Implicit")
			{
				builder.Append('~');
				builder.Append(ReturnType.GetXmlName());
			}
			
			return builder.ToString();
		}
		
//		/// <summary>A conversion operator.</summary>
//		public static explicit operator SortKey(MethodMember value)
//		{
//			return new SortKey{Name = value.Name, SubWeight = value.Arguments.Names.Length};
//		}
		
//		/// <summary>An addition operator.</summary>
//		public static MethodMember operator+(MethodMember lhs, MethodMember rhs)
//		{
//			return lhs;
//		}
		
		#region Internal Methods
		internal static string GetModifiers(MethodDefinition method)
		{
			var builder = new System.Text.StringBuilder();
			
			TypeDefinition td = (TypeDefinition) method.DeclaringType;
			if (!td.IsInterface)		// note that interface methods are normally decorated with 'public new abstract'
			{
				MethodAttributes attrs = method.Attributes;
				switch (attrs & MethodAttributes.MemberAccessMask)
				{
					case MethodAttributes.Compilercontrolled:
						builder.Append("compiler ");
						break;
					
					case MethodAttributes.Private:
						builder.Append("private ");
						break;
					
					case MethodAttributes.FamANDAssem:
						builder.Append("protected&internal ");
						break;
					
					case MethodAttributes.Assem:
						builder.Append("internal ");
						break;
					
					case MethodAttributes.Family:
						builder.Append("protected ");
						break;
					
					case MethodAttributes.FamORAssem:
						builder.Append("protected internal ");
						break;
					
					case MethodAttributes.Public:
						builder.Append("public ");
						break;
					
					default:
						Contract.Assert(false, "bad access: " + (attrs & MethodAttributes.MemberAccessMask));
						break;
				}
				
				if ((attrs & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot)
					builder.Append("new ");
				
				if ((attrs & MethodAttributes.Static) == MethodAttributes.Static)
					builder.Append("static ");
				
				if ((attrs & MethodAttributes.Abstract) == MethodAttributes.Abstract)
					builder.Append("abstract ");
				else if ((attrs & MethodAttributes.Virtual) == MethodAttributes.Virtual)
					if ((attrs & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot)
						builder.Append("override ");
					else if ((attrs & MethodAttributes.Final) == 0)
						builder.Append("virtual ");
						
				if ((attrs & MethodAttributes.Final) == MethodAttributes.Final)
					builder.Append("sealed ");
			}
			
			return builder.ToString();
		}
		
		internal static TypeName[] GetGenericArgs(Document doc, MethodDefinition method)
		{
			TypeName[] args = null;
			
			if (method.HasGenericParameters)
			{
				args = new TypeName[method.GenericParameters.Count];
				for (int i = 0; i < method.GenericParameters.Count; ++i)
				{
					args[i] = TypeName.Create(doc, method.GenericParameters[i]);
				}
			}
			
			return args;
		}
		#endregion
		
		#region Private Methods
		public string MemberName {get; private set;}

		private string DoGetMethodName(MethodDefinition method)
		{
			switch (method.Name)
			{
				case "op_Decrement":
					return "operator--";
				
				case "op_Increment":
					return "operator++";
				
				case "op_UnaryNegation":
					return "operator-";
				
				case "op_UnaryPlus":
					return "operator+";
				
				case "op_LogicalNot":
					return "operator!";
				
				case "op_AddressOf":
					return "operator&";
				
				case "op_OnesComplement":
					return "operator~";
								
				case "op_PointerDereference":
					return "operator*";
								
				case "op_Addition":
					return "operator+";
				
				case "op_Subtraction":
					return "operator-";
				
				case "op_Multiply":
					return "operator*";
				
				case "op_Division":
					return "operator/";
				
				case "op_Modulus":
					return "operator%";
				
				case "op_ExclusiveOr":
					return "operator^";
				
				case "op_BitwiseAnd":
					return "operator&";
				
				case "op_BitwiseOr":
					return "operator|";
				
				case "op_LogicalAnd":
					return "operator&&";
				
				case "op_LogicalOr":
					return "operator||";
				
				case "op_Assign":
					return "operator=";
				
				case "op_LeftShift":
					return "operator<<";
				
				case "op_RightShift":
					return "operator>>";
				
				case "op_Equality":
					return "operator==";
				
				case "op_Inequality":
					return "operator!=";
				
				case "op_GreaterThan":
					return "operator>";
				
				case "op_GreaterThanOrEqual":
					return "operator>=";
				
				case "op_LessThan":
					return "operator<";
				
				case "op_LessThanOrEqual":
					return "operator<=";
								
				case "op_MultiplicationAssignment":
					return "operator*=";
				
				case "op_SubtractionAssignment":
					return "operator-=";
				
				case "op_ExclusiveOrAssignment":
					return "operator^=";
				
				case "op_LeftShiftAssignment":
					return "operator<<=";
				
				case "op_RightShiftAssignment":
					return "operator>>=";
				
				case "op_ModulusAssignment":
					return "operator%=";
				
				case "op_AdditionAssignment":
					return "operator+=";
				
				case "op_BitwiseAndAssignment":
					return "operator&=";
				
				case "op_BitwiseOrAssignment":
					return "operator=";
				
				case "op_DivisionAssignment":
					return "operator/=";
				
				case "op_Implicit":
					return "implicit operator " + method.ReturnType.ReturnType.Name.StripBackTicks();
				
				case "op_Explicit":
					return "explicit operator " + method.ReturnType.ReturnType.Name.StripBackTicks();
				
				default:
					return method.Name;
			}
		}
		#endregion
	}
}
